package org.lc.cmd.impl;

import lombok.Getter;
import org.jline.builtins.ConfigurationPath;
import org.jline.console.CommandMethods;
import org.jline.console.CommandRegistry;
import org.jline.console.SystemRegistry;
import org.jline.console.impl.SystemRegistryImpl;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.Parser;
import org.jline.reader.impl.DefaultHighlighter;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Cursor;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.lc.cmd.Cmd;
import org.lc.cmd.CmdDataBridge;
import org.lc.cmd.exception.CmdException;
import org.lc.cmd.impl.bridge.JLineHttpClientBridge;
import org.lc.cmd.impl.bridge.OverallBridge;
import org.lc.cmd.plugin.scan.DefaultCommandRegistry;
import org.lc.cmd.plugin.scan.ResultManager;
import org.lc.cmd.util.CmdOutUtil;
import org.lc.cmd.util.ContextUtil;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.function.Consumer;


@Getter
public class TerminalRunner extends Cmd implements Closeable {
    @Getter
    private static final Path workDir = Paths.get(System.getProperty("user.dir"));

    private ResultManager resultManager;

    private Terminal terminal;

    LineReader reader;

    String prompt;

    ConfigurationPath configurationPath = new ConfigurationPath(workDir, workDir);

    SystemRegistry registry;

    CommandRegistry commandRegistry;

    Parser parser;

    private volatile boolean running = true;

    private CmdDataBridge bridge;

    public static TerminalRunner overAllRunner() {
        TerminalRunner terminal = new TerminalRunner();
        terminal.bridge = new OverallBridge(terminal);
        return terminal;
    }

    public static TerminalRunner httpRunner(String serverUrl) {
        TerminalRunner terminal = new TerminalRunner();
        terminal.bridge = new JLineHttpClientBridge(serverUrl, terminal);
        return terminal;
    }

    /**
     * 启动终端命令行监听执行
     * 只能在主线程运行，否则readLine的结果会不正确
     */
    @Override
    public void run(Consumer<String[]> springInit) throws IOException {
        if (bridge instanceof OverallBridge) {
            springInit.accept(args);
        }
        initAfterSpringStarted();
        while (running) {
            String line = reader.readLine(prompt);
            getBridge().sendCmdLine(line);
        }
    }

    @Override
    public CmdDataBridge getBridge() {
        return bridge;
    }

    private void initAfterSpringStarted() throws IOException {
        if (terminal == null) {
            terminal = TerminalBuilder.terminal();
        }
        if (parser == null) {
            parser = new DefaultParser();
        }
        if (registry == null) {
            registry = new SystemRegistryImpl(parser, terminal, TerminalRunner::getWorkDir, configurationPath);
            removeJLineCommand(registry);
        }
        if (commandRegistry == null) {
            commandRegistry = ContextUtil.getBean(CommandRegistry.class);
        }
        registry.setCommandRegistries(commandRegistry);
        if (reader == null) {
            reader = LineReaderBuilder.builder()
                    .terminal(terminal)
                    .completer(registry.completer())
                    .highlighter(new DefaultHighlighter())
                    .build();
            reader.setAutosuggestion(LineReader.SuggestionType.TAIL_TIP);
        }
        if (prompt == null) {
            prompt = "cmd > ";
        }
        if (resultManager == null) {
            resultManager = ContextUtil.getBean(ResultManager.class);
        }
    }

    public void stop() {
        running = false;
    }

    @Override
    public void close() throws IOException {
        running = false;
        if (terminal != null) {
            terminal.close();
        }
    }

    private void removeJLineCommand(SystemRegistry registry) {
        try {
            Field field = SystemRegistryImpl.class.getDeclaredField("commandExecute");
            field.setAccessible(true);
            @SuppressWarnings("unchecked")
            Map<String, CommandMethods> map = (Map<String, CommandMethods>) field.get(registry);
            map.clear();
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new CmdException("去除jline内置命令失败", e);
        }
    }
}
